--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
tests/backend/compare_benchmarks.py e5756d7ab065475a18acdcafc008a767fcff87e0 (e5756d7a) Text, 12.85 KB
T8b949e# SPDX-License-Identifier: 0BSD
T8b949e"""Smart benchmark regression gate for CI.
github-action-benchmark only supports a single ratio threshold. Sub-millisecond
SQLite ops on shared runners routinely swing 2-3x from scheduler noise, so a
flat ratio alert is useless. This script:
1. Loads current suite JSON (customSmallerIsBetter) and the cached baseline.
2. Applies noise-floor, absolute-delta, and adaptive-ratio heuristics.
3. Writes a human-readable summary and exits non-zero only on real regressions.
4. Optionally updates the baseline cache when the run is clean (or always when
--update-baseline is set), so the next push compares against a stable
median rather than a single noisy sample.
"""
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
Tff7b72import T7ee787argparse
Tff7b72import T7ee787json
Tff7b72import T7ee787os
Tff7b72import T7ee787sys
Tff7b72from T7ee787datetime Tff7b72import Te6edf3UTCTb4b4b4, Te6edf3datetime
Te6edf3_REPO_ROOT Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Tff7b72__file__Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ff..Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff..Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffpathTff7b72.Td2a8ffinsertTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3_REPO_ROOTTb4b4b4)
Tff7b72from T7ee787testsT7ee787.T7ee787backendT7ee787.T7ee787benchmarking_utils Tff7b72import Tb4b4b4( T8b949e# noqa: E402
Te6edf3parse_extra_statsTb4b4b4,
Te6edf3should_alert_regressionTb4b4b4,
Tb4b4b4)
T8b949e# Must stay present in comprehensive suite runs. Gate fails if a full-suite
T8b949e# baseline is missing any of these from the current JSON.
Te6edf3REQUIRED_BENCHES Tff7b72= Tffa657frozensetTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffDatabase InitializationTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffMessage Upsert (Batch of 100)Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffGet 100 Conversations ListTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffGet Conversations Slim List (handler)Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffGet Conversations Unread Filter (handler)Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffMark Conversation As ReadTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffGet Messages for Conversation (offset 500)Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffLog Telephone CallTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffGet Call History ListTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffNotification Add + Unread CountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffMissed Call Unread Count by TypeTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffDismiss Missed Call NotificationsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffConfig Get (50 keys)Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffGet Contacts ListTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ff_is_full_suite_baselineTb4b4b4(Te6edf3previousTb4b4b4)Tb4b4b4:
T8b949e"""True when previous looks like the comprehensive suite, not a unit fixture."""
Tff7b72return Ta5d6ff"Ta5d6ffDatabase InitializationTa5d6ff" Tff7b72in Te6edf3previous
Tff7b72def Td2a8ff_load_entriesTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3data Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657listTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3data
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
T8b949e# github-action-benchmark external-data shape
Tff7b72if Ta5d6ff"Ta5d6ffentriesTa5d6ff" Tff7b72in Te6edf3data Tff7b72and Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6ffentriesTa5d6ff"Tb4b4b4]Tb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
T8b949e# Prefer the first suite key (usually the tool name)
Tff7b72for Te6edf3_suiteTb4b4b4, Te6edf3entries Tff7b72in Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6ffentriesTa5d6ff"Tb4b4b4]Tff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3entriesTb4b4b4, Tffa657listTb4b4b4) Tff7b72and Te6edf3entriesTb4b4b4:
T8b949e# Prefer the newest commit's last values: entries are
T8b949e# historical lists of {commit, date, tool, benches}
Te6edf3latest Tff7b72= Te6edf3entriesTb4b4b4[Tff7b72-T79c0ff1Tb4b4b4]
Te6edf3benches Tff7b72= Te6edf3latestTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffbenchesTa5d6ff"Tb4b4b4) Tff7b72or Te6edf3latestTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffbenchmarksTa5d6ff"Tb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3benchesTb4b4b4, Tffa657listTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3benches
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3latestTb4b4b4, Tffa657listTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3latest
Tff7b72if Ta5d6ff"Ta5d6ffbenchesTa5d6ff" Tff7b72in Te6edf3data Tff7b72and Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6ffbenchesTa5d6ff"Tb4b4b4]Tb4b4b4, Tffa657listTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6ffbenchesTa5d6ff"Tb4b4b4]
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUnrecognized benchmark JSON shape in Tffd700{Te6edf3pathTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_index_by_nameTb4b4b4(Te6edf3entriesTb4b4b4)Tb4b4b4:
Te6edf3out Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3entry Tff7b72in Te6edf3entriesTb4b4b4:
Te6edf3name Tff7b72= Te6edf3entryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3nameTb4b4b4:
Tff7b72continue
Te6edf3outTb4b4b4[Te6edf3nameTb4b4b4] Tff7b72= Te6edf3entry
Tff7b72return Te6edf3out
Tff7b72def Td2a8ff_value_msTb4b4b4(Te6edf3entryTb4b4b4)Tb4b4b4:
Tff7b72return Tffa657floatTb4b4b4(Te6edf3entryTb4b4b4[Ta5d6ff"Ta5d6ffvalueTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72def Td2a8ffcompareTb4b4b4(
Te6edf3current_pathTb4b4b4,
Te6edf3previous_pathTb4b4b4,
Tff7b72*Tb4b4b4,
Te6edf3noise_floor_msTff7b72=T79c0ff0.5Tb4b4b4,
Te6edf3min_abs_delta_msTff7b72=T79c0ff1.5Tb4b4b4,
Te6edf3summary_pathTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3current Tff7b72= Te6edf3_index_by_nameTb4b4b4(Te6edf3_load_entriesTb4b4b4(Te6edf3current_pathTb4b4b4)Tb4b4b4)
Te6edf3previous Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72if Te6edf3previous_path Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3previous_pathTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3previous Tff7b72= Te6edf3_index_by_nameTb4b4b4(Te6edf3_load_entriesTb4b4b4(Te6edf3previous_pathTb4b4b4)Tb4b4b4)
Tff7b72except Tb4b4b4(Tf85149OSErrorTb4b4b4, Tf85149ValueErrorTb4b4b4, Te6edf3jsonTff7b72.Td2a8ffJSONDecodeErrorTb4b4b4, Tf85149KeyErrorTb4b4b4, Tf85149TypeErrorTb4b4b4) Tff7b72as Te6edf3excTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffWARNING: could not load previous baseline (Tffd700{Te6edf3excTffd700}Ta5d6ff); treating as first runTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3previous Tff7b72= Tb4b4b4{Tb4b4b4}
Te6edf3rows Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3alerts Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3improvements Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3skipped Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3coverage_alerts Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3enforce_coverage Tff7b72= Te6edf3_is_full_suite_baselineTb4b4b4(Te6edf3previousTb4b4b4)
Tff7b72for Te6edf3name Tff7b72in Tffa657sortedTb4b4b4(Te6edf3currentTb4b4b4)Tb4b4b4:
Te6edf3cur Tff7b72= Te6edf3currentTb4b4b4[Te6edf3nameTb4b4b4]
Te6edf3cur_ms Tff7b72= Te6edf3_value_msTb4b4b4(Te6edf3curTb4b4b4)
Te6edf3cur_stats Tff7b72= Te6edf3parse_extra_statsTb4b4b4(Te6edf3curTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffextraTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3prev Tff7b72= Te6edf3previousTff7b72.Td2a8ffgetTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72if Te6edf3prev Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3rowsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3nameTb4b4b4,
Ta5d6ff"Ta5d6ffcurrentTa5d6ff"Tb4b4b4: Te6edf3cur_msTb4b4b4,
Ta5d6ff"Ta5d6ffpreviousTa5d6ff"Tb4b4b4: Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffratioTa5d6ff"Tb4b4b4: Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffstatusTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffnewTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdetailTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffno previous baselineTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72continue
Te6edf3prev_ms Tff7b72= Te6edf3_value_msTb4b4b4(Te6edf3prevTb4b4b4)
Te6edf3prev_stats Tff7b72= Te6edf3parse_extra_statsTb4b4b4(Te6edf3prevTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffextraTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3ratio Tff7b72= Te6edf3cur_ms Tff7b72/ Te6edf3prev_ms Tff7b72if Te6edf3prev_ms Tff7b72> T79c0ff0 Tff7b72else Tff7b72None
Te6edf3alertTb4b4b4, Te6edf3detail Tff7b72= Te6edf3should_alert_regressionTb4b4b4(
Te6edf3cur_msTb4b4b4,
Te6edf3prev_msTb4b4b4,
Te6edf3noise_floor_msTff7b72=Te6edf3noise_floor_msTb4b4b4,
Te6edf3min_abs_delta_msTff7b72=Te6edf3min_abs_delta_msTb4b4b4,
Te6edf3current_cvTff7b72=Te6edf3cur_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcvTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3previous_cvTff7b72=Te6edf3prev_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcvTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3alertTb4b4b4:
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffREGRESSIONTa5d6ff"
Te6edf3alertsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72elif Tb4b4b4(
Te6edf3ratio Tff7b72is Tff7b72not Tff7b72None
Tff7b72and Te6edf3ratio Tff7b72< T79c0ff0.85
Tff7b72and Tb4b4b4(Te6edf3prev_ms Tff7b72- Te6edf3cur_msTb4b4b4) Tff7b72>Tff7b72= Te6edf3min_abs_delta_ms
Tb4b4b4)Tb4b4b4:
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffimprovedTa5d6ff"
Te6edf3improvementsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72elif Tb4b4b4(
Ta5d6ff"Ta5d6ffnoise floorTa5d6ff" Tff7b72in Te6edf3detail
Tff7b72or Ta5d6ff"Ta5d6ffabs deltaTa5d6ff" Tff7b72in Te6edf3detail
Tff7b72or Ta5d6ff"Ta5d6ffwithin adaptiveTa5d6ff" Tff7b72in Te6edf3detail
Tb4b4b4)Tb4b4b4:
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffnoiseTa5d6ff"
Te6edf3skippedTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffokTa5d6ff"
Te6edf3rowsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3nameTb4b4b4,
Ta5d6ff"Ta5d6ffcurrentTa5d6ff"Tb4b4b4: Te6edf3cur_msTb4b4b4,
Ta5d6ff"Ta5d6ffpreviousTa5d6ff"Tb4b4b4: Te6edf3prev_msTb4b4b4,
Ta5d6ff"Ta5d6ffratioTa5d6ff"Tb4b4b4: Te6edf3ratioTb4b4b4,
Ta5d6ff"Ta5d6ffstatusTa5d6ff"Tb4b4b4: Te6edf3statusTb4b4b4,
Ta5d6ff"Ta5d6ffdetailTa5d6ff"Tb4b4b4: Te6edf3detailTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3missing Tff7b72= Tffa657sortedTb4b4b4(Tffa657setTb4b4b4(Te6edf3previousTb4b4b4) Tff7b72- Tffa657setTb4b4b4(Te6edf3currentTb4b4b4)Tb4b4b4)
Te6edf3coverage_seen Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72for Te6edf3name Tff7b72in Te6edf3missingTb4b4b4:
Te6edf3detail Tff7b72= Ta5d6ff"Ta5d6ffpresent in baseline onlyTa5d6ff"
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffremovedTa5d6ff"
Tff7b72if Te6edf3enforce_coverageTb4b4b4:
Te6edf3coverage_alertsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3coverage_seenTff7b72.Td2a8ffaddTb4b4b4(Te6edf3nameTb4b4b4)
Tff7b72if Te6edf3name Tff7b72in Te6edf3REQUIRED_BENCHESTb4b4b4:
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffMISSINGTa5d6ff"
Te6edf3detail Tff7b72= Ta5d6ff"Ta5d6ffrequired bench absent from current suiteTa5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3status Tff7b72= Ta5d6ff"Ta5d6ffREMOVEDTa5d6ff"
Te6edf3detail Tff7b72= Ta5d6ff"Ta5d6ffdropped from suite vs full baselineTa5d6ff"
Te6edf3rowsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3nameTb4b4b4,
Ta5d6ff"Ta5d6ffcurrentTa5d6ff"Tb4b4b4: Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffpreviousTa5d6ff"Tb4b4b4: Te6edf3_value_msTb4b4b4(Te6edf3previousTb4b4b4[Te6edf3nameTb4b4b4]Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffratioTa5d6ff"Tb4b4b4: Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffstatusTa5d6ff"Tb4b4b4: Te6edf3statusTb4b4b4,
Ta5d6ff"Ta5d6ffdetailTa5d6ff"Tb4b4b4: Te6edf3detailTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3check_required Tff7b72= Te6edf3enforce_coverage Tff7b72or Tb4b4b4(
Tff7b72not Te6edf3previous Tff7b72and Ta5d6ff"Ta5d6ffDatabase InitializationTa5d6ff" Tff7b72in Te6edf3current
Tb4b4b4)
Tff7b72if Te6edf3check_requiredTb4b4b4:
Tff7b72for Te6edf3name Tff7b72in Tffa657sortedTb4b4b4(Te6edf3REQUIRED_BENCHES Tff7b72- Tffa657setTb4b4b4(Te6edf3currentTb4b4b4) Tff7b72- Te6edf3coverage_seenTb4b4b4)Tb4b4b4:
Te6edf3coverage_alertsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3rowsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3nameTb4b4b4,
Ta5d6ff"Ta5d6ffcurrentTa5d6ff"Tb4b4b4: Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffpreviousTa5d6ff"Tb4b4b4: Tb4b4b4(
Te6edf3_value_msTb4b4b4(Te6edf3previousTb4b4b4[Te6edf3nameTb4b4b4]Tb4b4b4) Tff7b72if Te6edf3name Tff7b72in Te6edf3previous Tff7b72else Tff7b72None
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffratioTa5d6ff"Tb4b4b4: Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffstatusTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffMISSINGTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdetailTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrequired bench absent from current suiteTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3lines Tff7b72= Tb4b4b4[
Ta5d6ff"Ta5d6ffMeshChatX Backend Benchmark GateTa5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffCurrent: Tffd700{Te6edf3current_pathTffd700}Ta5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffPrevious: Tffd700{Te6edf3previous_path Tff7b72or Ta5d6ff'Ta5d6ff(none)Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffNoise floor: Tffd700{Te6edf3noise_floor_msTffd700}Ta5d6ff ms | Min abs delta: Tffd700{Te6edf3min_abs_delta_msTffd700}Ta5d6ff msTa5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffCoverage enforce: Tffd700{Ta5d6ff'Ta5d6ffyesTa5d6ff' Tff7b72if Te6edf3check_required Tff7b72else Ta5d6ff'Ta5d6ffnoTa5d6ff'Tffd700}Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Tffd700{Ta5d6ff'Ta5d6ffBenchmarkTa5d6ff'Tffd700:Ta5d6ff42Tffd700}Ta5d6ff Tffd700{Ta5d6ff'Ta5d6ffCurrTa5d6ff'Tffd700:Ta5d6ff>10Tffd700}Ta5d6ff Tffd700{Ta5d6ff'Ta5d6ffPrevTa5d6ff'Tffd700:Ta5d6ff>10Tffd700}Ta5d6ff Tffd700{Ta5d6ff'Ta5d6ffRatioTa5d6ff'Tffd700:Ta5d6ff>8Tffd700}Ta5d6ff StatusTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff-Ta5d6ff" Tff7b72* T79c0ff90Tb4b4b4,
Tb4b4b4]
Tff7b72for Te6edf3row Tff7b72in Te6edf3rowsTb4b4b4:
Te6edf3cur_s Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3rowTb4b4b4[Ta5d6ff'Ta5d6ffcurrentTa5d6ff'Tb4b4b4]Tffd700:Ta5d6ff.3fTffd700}Ta5d6ff" Tff7b72if Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffcurrentTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else Ta5d6ff"Ta5d6ff-Ta5d6ff"
Te6edf3prev_s Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3rowTb4b4b4[Ta5d6ff'Ta5d6ffpreviousTa5d6ff'Tb4b4b4]Tffd700:Ta5d6ff.3fTffd700}Ta5d6ff" Tff7b72if Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffpreviousTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else Ta5d6ff"Ta5d6ff-Ta5d6ff"
Te6edf3ratio_s Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3rowTb4b4b4[Ta5d6ff'Ta5d6ffratioTa5d6ff'Tb4b4b4]Tffd700:Ta5d6ff.2fTffd700}Ta5d6ffxTa5d6ff" Tff7b72if Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffratioTa5d6ff"Tb4b4b4] Tff7b72is Tff7b72not Tff7b72None Tff7b72else Ta5d6ff"Ta5d6ff-Ta5d6ff"
Te6edf3linesTff7b72.Td2a8ffappendTb4b4b4(
Ta5d6fffTa5d6ff"Tffd700{Te6edf3rowTb4b4b4[Ta5d6ff'Ta5d6ffnameTa5d6ff'Tb4b4b4]Tb4b4b4[Tb4b4b4:T79c0ff42Tb4b4b4]Tffd700:Ta5d6ff42Tffd700}Ta5d6ff Tffd700{Te6edf3cur_sTffd700:Ta5d6ff>10Tffd700}Ta5d6ff Tffd700{Te6edf3prev_sTffd700:Ta5d6ff>10Tffd700}Ta5d6ff Tffd700{Te6edf3ratio_sTffd700:Ta5d6ff>8Tffd700}Ta5d6ff Ta5d6ff"
Ta5d6fffTa5d6ff"Tffd700{Te6edf3rowTb4b4b4[Ta5d6ff'Ta5d6ffstatusTa5d6ff'Tb4b4b4]Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3rowTb4b4b4[Ta5d6ff"Ta5d6ffstatusTa5d6ff"Tb4b4b4] Tff7b72in Tb4b4b4{Ta5d6ff"Ta5d6ffREGRESSIONTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffREMOVEDTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffMISSINGTa5d6ff"Tb4b4b4}Tb4b4b4:
Te6edf3linesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff -> Tffd700{Te6edf3rowTb4b4b4[Ta5d6ff'Ta5d6ffdetailTa5d6ff'Tb4b4b4]Tffd700}Ta5d6ff"Tb4b4b4)
Te6edf3linesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ff-Ta5d6ff" Tff7b72* T79c0ff90Tb4b4b4)
Te6edf3linesTff7b72.Td2a8ffappendTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffRegressions: Tffd700{Tffa657lenTb4b4b4(Te6edf3alertsTb4b4b4)Tffd700}Ta5d6ff | Improvements: Tffd700{Tffa657lenTb4b4b4(Te6edf3improvementsTb4b4b4)Tffd700}Ta5d6ff | Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffNoise-skipped: Tffd700{Tffa657lenTb4b4b4(Te6edf3skippedTb4b4b4)Tffd700}Ta5d6ff | New: Ta5d6ff"
Ta5d6fffTa5d6ff"Tffd700{Tffa657sumTb4b4b4(T79c0ff1 Tff7b72for Te6edf3r Tff7b72in Te6edf3rows Tff7b72if Te6edf3rTb4b4b4[Ta5d6ff'Ta5d6ffstatusTa5d6ff'Tb4b4b4] Tff7b72== Ta5d6ff'Ta5d6ffnewTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff | Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffCoverage: Tffd700{Tffa657lenTb4b4b4(Te6edf3coverage_alertsTb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3alerts Tff7b72or Te6edf3coverage_alertsTb4b4b4:
Te6edf3parts Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Te6edf3alertsTb4b4b4:
Te6edf3partsTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffregressions: Ta5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3alertsTb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3coverage_alertsTb4b4b4:
Te6edf3partsTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffcoverage: Ta5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff, Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3coverage_alertsTb4b4b4)Tb4b4b4)
Te6edf3linesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffALERT: Ta5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff | Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3partsTb4b4b4)Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3linesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffNo actionable regressions.Ta5d6ff"Tb4b4b4)
Te6edf3text Tff7b72= Ta5d6ff"Tffea00\nTa5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3linesTb4b4b4) Tff7b72+ Ta5d6ff"Tffea00\nTa5d6ff"
Tffa657printTb4b4b4(Te6edf3textTb4b4b4, Te6edf3endTff7b72=Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3summary_pathTb4b4b4:
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3summary_pathTb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72with Tffa657openTb4b4b4(Te6edf3summary_pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3textTb4b4b4)
T8b949e# Also append to GitHub job summary when available
Te6edf3gh_summary Tff7b72= Te6edf3osTff7b72.Td2a8ffenvironTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffGITHUB_STEP_SUMMARYTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3gh_summaryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3gh_summaryTb4b4b4, Ta5d6ff"Ta5d6ffaTa5d6ff"Tb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ff"Ta5d6ff## Benchmark gateTffea00\nTffea00\nTa5d6ff```Tffea00\nTa5d6ff"Tb4b4b4)
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3textTb4b4b4)
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ff"Ta5d6ff```Tffea00\nTa5d6ff"Tb4b4b4)
Tff7b72return T79c0ff1 Tff7b72if Tb4b4b4(Te6edf3alerts Tff7b72or Te6edf3coverage_alertsTb4b4b4) Tff7b72else T79c0ff0Tb4b4b4, Te6edf3rows
Tff7b72def Td2a8ffupdate_baselineTb4b4b4(
Te6edf3current_pathTb4b4b4,
Te6edf3baseline_pathTb4b4b4,
Te6edf3suite_nameTff7b72=Ta5d6ff"Ta5d6ffMeshChatX Backend BenchmarksTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4:
T8b949e"""Write/merge current results into github-action-benchmark external-data JSON."""
Te6edf3current_entries Tff7b72= Te6edf3_load_entriesTb4b4b4(Te6edf3current_pathTb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3baseline_pathTb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3commit Tff7b72= Te6edf3osTff7b72.Td2a8ffenvironTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffGITHUB_SHATa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fflocalTa5d6ff"Tb4b4b4)
Te6edf3date_ms Tff7b72= Tffa657intTb4b4b4(Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4) Tff7b72* T79c0ff1000Tb4b4b4)
Te6edf3record Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffcommitTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffidTa5d6ff"Tb4b4b4: Te6edf3commitTb4b4b4,
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Te6edf3osTff7b72.Td2a8ffenvironTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffBENCHMARK_COMMIT_MESSAGETa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffbenchmark runTa5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffurlTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffdateTa5d6ff"Tb4b4b4: Te6edf3date_msTb4b4b4,
Ta5d6ff"Ta5d6fftoolTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffcustomSmallerIsBetterTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffbenchesTa5d6ff"Tb4b4b4: Te6edf3current_entriesTb4b4b4,
Tb4b4b4}
Te6edf3data Tff7b72= Tb4b4b4{Ta5d6ff"Ta5d6fflastUpdateTa5d6ff"Tb4b4b4: Te6edf3date_msTb4b4b4, Ta5d6ff"Ta5d6ffrepoUrlTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffentriesTa5d6ff"Tb4b4b4: Tb4b4b4{Te6edf3suite_nameTb4b4b4: Tb4b4b4[Tb4b4b4]Tb4b4b4}Tb4b4b4}
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3baseline_pathTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3baseline_pathTb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3existing Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3existingTb4b4b4, Tffa657dictTb4b4b4) Tff7b72and Ta5d6ff"Ta5d6ffentriesTa5d6ff" Tff7b72in Te6edf3existingTb4b4b4:
Te6edf3data Tff7b72= Te6edf3existing
Tff7b72except Tb4b4b4(Tf85149OSErrorTb4b4b4, Te6edf3jsonTff7b72.Td2a8ffJSONDecodeErrorTb4b4b4)Tb4b4b4:
Tff7b72pass
Te6edf3entries Tff7b72= Te6edf3dataTff7b72.Td2a8ffsetdefaultTb4b4b4(Ta5d6ff"Ta5d6ffentriesTa5d6ff"Tb4b4b4, Tb4b4b4{Tb4b4b4}Tb4b4b4)Tff7b72.Td2a8ffsetdefaultTb4b4b4(Te6edf3suite_nameTb4b4b4, Tb4b4b4[Tb4b4b4]Tb4b4b4)
T8b949e# Keep a short history so cache stays small; compare uses the latest.
Te6edf3entriesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3recordTb4b4b4)
Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6ffentriesTa5d6ff"Tb4b4b4]Tb4b4b4[Te6edf3suite_nameTb4b4b4] Tff7b72= Te6edf3entriesTb4b4b4[Tff7b72-T79c0ff20Tb4b4b4:Tb4b4b4]
Te6edf3dataTb4b4b4[Ta5d6ff"Ta5d6fflastUpdateTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3date_ms
Tff7b72with Tffa657openTb4b4b4(Te6edf3baseline_pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3jsonTff7b72.Td2a8ffdumpTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3fTb4b4b4, Te6edf3indentTff7b72=T79c0ff2Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffBaseline updated: Tffd700{Te6edf3baseline_pathTffd700}Ta5d6ff (Tffd700{Tffa657lenTb4b4b4(Te6edf3current_entriesTb4b4b4)Tffd700}Ta5d6ff metrics)Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffmainTb4b4b4(Te6edf3argvTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Te6edf3parser Tff7b72= Te6edf3argparseTff7b72.Td2a8ffArgumentParserTb4b4b4(Te6edf3descriptionTff7b72=Ta5d6ff"Ta5d6ffSmart CI benchmark regression gateTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--currentTa5d6ff"Tb4b4b4, Te6edf3requiredTff7b72=Tff7b72TrueTb4b4b4, Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffCurrent bench JSON pathTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ff--previousTa5d6ff"Tb4b4b4,
Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffPrevious baseline JSON (github-action-benchmark external-data or flat list)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ff--baseline-outTa5d6ff"Tb4b4b4,
Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffIf set, write/update baseline cache at this path after a clean runTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ff--update-baselineTa5d6ff"Tb4b4b4,
Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffAlways update baseline even when regressions are foundTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--noise-floor-msTa5d6ff"Tb4b4b4, Tffa657typeTff7b72=Tffa657floatTb4b4b4, Te6edf3defaultTff7b72=T79c0ff0.5Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--min-abs-delta-msTa5d6ff"Tb4b4b4, Tffa657typeTff7b72=Tffa657floatTb4b4b4, Te6edf3defaultTff7b72=T79c0ff1.5Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--summaryTa5d6ff"Tb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4, Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffWrite text summary to PATHTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ff--suite-nameTa5d6ff"Tb4b4b4,
Te6edf3defaultTff7b72=Ta5d6ff"Ta5d6ffMeshChatX Backend BenchmarksTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffSuite key inside external-data JSONTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3args Tff7b72= Te6edf3parserTff7b72.Td2a8ffparse_argsTb4b4b4(Te6edf3argvTb4b4b4)
Te6edf3codeTb4b4b4, Te6edf3_rows Tff7b72= Te6edf3compareTb4b4b4(
Te6edf3argsTff7b72.Td2a8ffcurrentTb4b4b4,
Te6edf3argsTff7b72.Td2a8ffpreviousTb4b4b4,
Te6edf3noise_floor_msTff7b72=Te6edf3argsTff7b72.Td2a8ffnoise_floor_msTb4b4b4,
Te6edf3min_abs_delta_msTff7b72=Te6edf3argsTff7b72.Td2a8ffmin_abs_delta_msTb4b4b4,
Te6edf3summary_pathTff7b72=Te6edf3argsTff7b72.Td2a8ffsummaryTb4b4b4,
Tb4b4b4)
Te6edf3should_update Tff7b72= Te6edf3argsTff7b72.Td2a8ffbaseline_out Tff7b72and Tb4b4b4(Te6edf3argsTff7b72.Td2a8ffupdate_baseline Tff7b72or Te6edf3code Tff7b72== T79c0ff0Tb4b4b4)
Tff7b72if Te6edf3should_updateTb4b4b4:
Te6edf3update_baselineTb4b4b4(Te6edf3argsTff7b72.Td2a8ffcurrentTb4b4b4, Te6edf3argsTff7b72.Td2a8ffbaseline_outTb4b4b4, Te6edf3suite_nameTff7b72=Te6edf3argsTff7b72.Td2a8ffsuite_nameTb4b4b4)
Tff7b72elif Te6edf3argsTff7b72.Td2a8ffbaseline_out Tff7b72and Te6edf3code Tff7b72!= T79c0ff0Tb4b4b4:
Tffa657printTb4b4b4(
Ta5d6ff"Ta5d6ffBaseline not updated (regressions present). Re-run with --update-baseline to force.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3code
Tff7b72if Tff7b72__name__ Tff7b72== Ta5d6ff"Ta5d6ff__main__Ta5d6ff"Tb4b4b4:
Tff7b72raise Tf85149SystemExitTb4b4b4(Te6edf3mainTb4b4b4(Tb4b4b4)Tb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────